/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package servergui;

import DAO_BUS.*;
import DataObject.MyColumn;
import DataObject.ServerInfo;
import DataObject.TableObject;
import Shared.PackageTransfer;
import convert_crypto.CryptoFile;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *
 * @author Backkom
 */
public class ServerScreen extends JFrame implements ActionListener, Runnable {

    private int PORT = 3333;
    final String passSV = "server-0912328";
    public Vector<File> database;
    Vector tables;
    public Vector<ServerInfo> serverinfo;
    private Vector<Document> docs;
    File Store;
    JDialog info;
    JTextField portSV;
    JPasswordField svpass;
    JTextField storeAR;
    JLabel lber;

    private void CreateAndShowPanel(JPanel panel, String title) {
        info.setBounds(ScreenWidth / 2 - 300, ScreenHeight / 2 - 150, 600, 220);
        info.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        info.setTitle(title);
        info.setResizable(false);
        info.setContentPane(panel);
        info.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                ServerScreen.this.dispose();
                System.exit(0);
            }
        });
        // info.pack();
        // info.setVisible(true);
    }

    private JPanel createPane() {
        JPanel Row = new JPanel();
        JLabel space = new JLabel();
        Row.setLayout(new BoxLayout(Row, BoxLayout.X_AXIS));
        JLabel port = new JLabel("Port:");
        port.setPreferredSize(new Dimension(80, 30));
        portSV = new JTextField();;
        Row.add(port);
        Row.add(portSV);
        JPanel Row1 = new JPanel();
        Row1.setLayout(new BoxLayout(Row1, BoxLayout.X_AXIS));
        JLabel pass = new JLabel("Password:");
        pass.setPreferredSize(new Dimension(80, 30));
        svpass = new JPasswordField();
        Row1.add(pass);
        Row1.add(svpass);
        JPanel Row2 = new JPanel();
        Row2.setLayout(new BoxLayout(Row2, BoxLayout.X_AXIS));
        JLabel store = new JLabel("Data Store:");
        store.setPreferredSize(new Dimension(80, 30));
        storeAR = new JTextField();
        JButton openButton = new JButton("Browse");
        openButton.setActionCommand("browse");
        openButton.addActionListener(this);
        Row2.add(store);
        Row2.add(storeAR);
        Row2.add(openButton);
        JPanel bton = new JPanel(new GridLayout(1, 2));
        JButton okButton = new JButton("OK");
        okButton.setActionCommand("ok");
        okButton.setPreferredSize(new Dimension(80, 40));
        okButton.addActionListener(this);
        JButton defButton = new JButton("Default");
        defButton.setActionCommand("default");
        defButton.setPreferredSize(new Dimension(100, 40));
        defButton.addActionListener(this);
        bton.add(okButton);
        bton.add(defButton);
        portSV.setPreferredSize(new Dimension(100, 30));
        svpass.setPreferredSize(new Dimension(100, 30));
        storeAR.setPreferredSize(new Dimension(100, 30));
        JPanel pane = new JPanel();
        pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
        lber = new JLabel("*Fill all info pleas!");
        lber.setPreferredSize(new Dimension(400, 40));
        lber.setForeground(Color.BLUE);
        pane.add(lber);
        pane.add(Row);
        pane.add(Row2);
        pane.add(Row1);
        pane.add(bton);
        return pane;
    }

    public class ClientInfo {

        public InputStream in;
        public OutputStream out = null;
        public SSLSocket socket;

        public ClientInfo(SSLSocket SSLSocket) {
            try {
                socket = SSLSocket;
                in = socket.getInputStream();
                out = socket.getOutputStream();
            } catch (IOException ex) {
            }
        }
    };
    public SSLServerSocket ss;
    public boolean active = true;
    public boolean maintain = false;
    boolean first = true;
    JMenu file;
    ExecutorService exe;
    ExecutorService exeCached;
    Vector<SSLSocket> clientList;
    JButton btnManage;
    JTextArea Output;
    int ScreenWidth;
    int ScreenHeight;

    public ServerScreen(String title) throws HeadlessException {
        super(title);
        JFrame.setDefaultLookAndFeelDecorated(true);
        Toolkit tool = Toolkit.getDefaultToolkit();
        info = new JDialog(this, title, true);
        ScreenWidth = tool.getScreenSize().width;
        ScreenHeight = tool.getScreenSize().height;
        Store = new File("Data");
        serverinfo = new Vector<>();
        CreateAndShowPanel(createPane(), "ServerInfo");
        Init();
    }

    private void setPanel(JPanel panel) {
        this.getContentPane().removeAll();
        this.setContentPane(panel);
        this.paintAll(this.getGraphics());
    }

    private void Show() {
        pack();
        setVisible(true);
        Start();
    }

    public void Start() {
        serverinfo.clear();
        serverinfo = getDatabaseInfo();
        active = true;
        maintain = false;
        exe = Executors.newSingleThreadExecutor();
        if (exeCached == null) {
            exeCached = Executors.newCachedThreadPool();
        }
        exe.execute(this);
    }

    private void Init() {
        JMenuBar menu = InitMenu();
        this.setJMenuBar(menu);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(null);
        this.setPreferredSize(new Dimension(ScreenWidth - 500, 700));
        menu.setPreferredSize(new Dimension(this.getWidth(), 25));
        this.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                //Dispose thread
                SaveDatabase();
                exe.shutdown();
                active = false;
                for (SSLSocket i : clientList) {
                    if (!i.isClosed()) {
                        try {
                            i.close();
                        } catch (IOException ex) {
                            continue;
                        }
                    }
                }
                clientList.clear();
                try {
                    ss.close();
                } catch (IOException ex) {
                }
                ServerScreen.this.dispose();
                super.windowClosing(e);
            }
        });
        JPanel panel = new JPanel(new BorderLayout());
        Output = new JTextArea();
        Output.setEditable(false);
        Output.setFont(new Font("Times New Roman", Font.BOLD, 16));
        Output.setForeground(Color.GREEN);
        Output.setBackground(Color.DARK_GRAY);
        clientList = new Vector<>();
        LoadDB();
        panel.add(Output);
        setPanel(panel);
        Show();
    }

    private Vector LoadDatabase(Document doc) {
        Vector<MyTable> tables = new Vector();
        NodeList list = doc.getElementsByTagName("Table");
        for (int i = 0; i < list.getLength(); i++) {
            Element element = (Element) list.item(i);
            MyTable table = new MyTable(element);
            tables.add(table);
        }
        return tables;
    }

    private void LoadDB() {
        database = new Vector<>();
        tables = new Vector();
        docs = new Vector<Document>();
        File[] tempFs = Store.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".en")) {
                    return true;
                }
                return false;
            }
        });
        for (File i : tempFs) {
            database.add(i);
        }
        LoadAllDatabase();

    }

    private boolean CreateProcessUnit(SSLSocket cl) {

        TransferClientRun transfer = new TransferClientRun(cl, this);
        boolean b;
        if (b = transfer.canRun) {
            clientList.add(cl);
            exeCached.execute(transfer);
        }
        return b;
    }

    private JMenuBar InitMenu() {
        JMenuBar menu = new JMenuBar();
        file = new JMenu("File");
        JMenuItem manage = new JMenuItem("Database Manage");
        manage.addActionListener(this);
        manage.setActionCommand("openmng");
        JMenuItem save = new JMenuItem("Save Data");
        save.addActionListener(this);
        save.setActionCommand("savedata");
        JMenuItem close = new JMenuItem("Close");
        close.addActionListener(this);
        close.setActionCommand("close");
        file.add(manage);
        file.add(save);
        file.add(close);
        menu.add(file);
        return menu;
    }

    private void DisposeThread(boolean alert) {
        try {
            active = false;
            exe.shutdown();
            ss.close();
            maintain = true;
        } catch (IOException ex) {
            System.out.println("Error when close Server!");
        }
    }

    @Override
    public void run() {
        try {
            SSLServerSocketFactory sf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            ss = (SSLServerSocket) sf.createServerSocket(PORT);
        } catch (IOException ex) {
            Output.append("Cannot create Server SSLSocket!\n");
        }
        do {
            try {
                //Accepts Client    
                SSLSocket client = (SSLSocket) ss.accept();
                Output.append("Connect with : " + client.getLocalAddress().toString()
                        + " at port " + Integer.toString(client.getPort()) + ".\n");
                if (!CreateProcessUnit(client)) {
                    Output.append("Create Process Fail: " + client.getInetAddress().toString()
                            + " at port " + Integer.toString(client.getPort()) + ".\n"
                            + "Close Connect with " + client.getInetAddress().toString() + ".\n");
                    client.close();
                }
            } catch (IOException ex) {
                continue;
            }
        } while (active);
    }

    public synchronized String Process(String dbName, String query, Vector result) {
        Document doc = null;
        Vector<MyTable> table = new Vector<>();
        Vector datas = null;
        for (int i = 0; i < database.size(); i++) {
            if (database.get(i).getName().equalsIgnoreCase(dbName + ".en")) {
                doc = docs.get(i);
                table = (Vector<MyTable>) tables.elementAt(i);
                break;
            }
        }

        MySentence sen = Database_Server.GetSentenceFromString(query);
        if (sen instanceof SelectSentence) {
            datas = new Vector();
            ((SelectSentence) sen).Excute(doc,
                    table, datas);
            if (datas.isEmpty()) {
                return "Not have result";
            }
            result.addAll(datas);
        } else if (sen instanceof InsertSentence || sen instanceof UpdateSentence
                || sen instanceof DeleteSentence || sen instanceof CreateSentence) {
            boolean sc = sen.Excute(doc,
                    table);
            if (!sc) {
                result = null;
                return "Failed";
            }
        } else {
            result = null;
            return "Query not support.";
        }

        return "Success";
    }

    public synchronized Vector Process(PackageTransfer request, PackageTransfer success) {
        Vector result = null;
        if (maintain) {
            success.header = "maintain";
        } else {
            success.header = "err";
            boolean sc = false;
            String header = request.header;
            if (header.startsWith("query")) {
                String dbName = header.replace("query", "");
                Document doc = null;
                Vector<MyTable> table = new Vector<>();
                for (int i = 0; i < database.size(); i++) {
                    if (database.get(i).getName().equalsIgnoreCase(dbName + ".en")) {
                        doc = docs.get(i);
                        table = (Vector<MyTable>) tables.elementAt(i);
                    }
                }
                //Excute query            
                String query = (String) request.datas;
                MySentence sen = Database_Server.GetSentenceFromString(query);
                if (sen instanceof CreateSentence) {
                    return result;
                }
                if (sen instanceof SelectSentence) {
                    result = new Vector();
                    sc = ((SelectSentence) sen).Excute(doc,
                            table, result);
                } else if (sen instanceof InsertSentence || sen instanceof UpdateSentence
                        || sen instanceof DeleteSentence) {
                    sc = sen.Excute(doc,
                            table);
                } else {
                    return result;
                }
            }
            if (sc) {
                success.header = "success";
            }
            return result;
        }
        return result;
    }

    MyTable getTable(String dbName, String tbName) {
        for (int i = 0; i < database.size(); i++) {
            if (database.get(i).getName().equalsIgnoreCase(dbName + ".en")) {
                for (MyTable j : (Vector<MyTable>) tables.elementAt(i)) {
                    if (j.GetName().compareToIgnoreCase(tbName) == 0) {
                        return j;
                    }
                }
            }
        }
        return null;
    }

    void Close() {
        //Dispose thread
        SaveDatabase();
        exe.shutdown();
        active = false;
        for (SSLSocket i : clientList) {
            if (!i.isClosed()) {
                try {
                    i.close();
                } catch (IOException ex) {
                    continue;
                }
            }
        }
        clientList.clear();
        try {
            ss.close();
        } catch (IOException ex) {
        }
        this.dispose();
        System.exit(0);
    }

    public String AddRow(String dbName, String tbName, TableObject row) {
        MyTable table = getTable(dbName, tbName);
        Document doc = null;
        for (int i = 0; i < database.size(); i++) {
            if (database.get(i).getName().equalsIgnoreCase(dbName + ".en")) {
                doc = docs.get(i);
            }
        }
        String error = table.AddRow(doc, row);

        return error;
    }

    public String DeleteRow(String dbName, String tbName, TableObject row) {
        MyTable table = getTable(dbName, tbName);
        String error = table.DeleteRow(row);
        return error;
    }

    public String UpdateRow(String dbName, String tbName, TableObject oldRow,
            String columnName, Object value, String[] Header) {
        MyTable table = getTable(dbName, tbName);
        String error = table.UpdateRow(oldRow, columnName, value);
        return error;
    }

    public void SaveDatabase() {
        try {
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();

            for (int i = 0; i < database.size(); i++) {
                File f = new File(database.get(i).getAbsolutePath().replace(".en", ".xml"));
                Result result = new StreamResult(f);
                Source source = new DOMSource(docs.get(i));
                transformer.transform(source, result);
                CryptoFile.Encrypt("30532228", f.getAbsolutePath());
            }
        } catch (TransformerException ex) {
            System.out.println(ex.toString());
        }
    }

    public void LoadAllDatabase() {
        try {
            DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
            //Load all databases, tables            
            for (int i = 0; i < database.size(); i++) {
                Vector<MyTable> tb = new Vector<>();
                CryptoFile.Decrypt("30532228", database.get(i).getAbsolutePath());
                File f = new File(database.get(i).getAbsolutePath().replace(".en", ".xml"));
                docs.add(docBuilder.parse(f));
                tb = LoadDatabase(docs.get(i));
                tables.add(tb);
                f.delete();
            }
        } catch (SAXException | IOException | ParserConfigurationException ex) {
            System.out.println(ex.toString());
        }
    }

    void SaveDesign(String databaseName, String tableName, Vector<TableObject> listObject, boolean isNew) {
        int indexDatabase = -1;
        for (int i = 0; i < database.size(); i++) {
            if (database.get(i).getName().equalsIgnoreCase(databaseName + ".en")) {
                indexDatabase = i;
                break;
            }
        }

        if (indexDatabase == -1) {
            return;
        }

        Vector<MyTable> tablesOfDatabase = (Vector<MyTable>) tables.get(indexDatabase);
        MyTable table = null;
        int indexTable = -1;
        for (int i = 0; i < tablesOfDatabase.size(); i++) {
            MyTable testTable = tablesOfDatabase.get(i);
            if (testTable.GetName().compareTo(tableName) == 0) {
                table = testTable;
                indexTable = i;
                break;
            }
        }

        if (table == null) {
            return;
        }

        if (isNew) {

            Element eleOldHeader = (Element) table.ele.getElementsByTagName("Header").item(0);
            Element eleOldType = (Element) table.ele.getElementsByTagName("Type").item(0);

            Element eleHeader = docs.get(indexDatabase).createElement("Header");
            Element eleType = docs.get(indexDatabase).createElement("Type");
            for (int i = 0; i < listObject.size(); i++) {
                TableObject tableObject = listObject.get(i);
                eleHeader.setAttribute(tableObject.getAttributeAt(0).toString(), String.valueOf(i));
                eleType.setAttribute(tableObject.getAttributeAt(0).toString(), tableObject.getAttributeAt(1).toString());
                boolean isKey = Boolean.valueOf(tableObject.getAttributeAt(2).toString());
                if (isKey) {
                    table.ele.setAttribute("PrimaryKey", tableObject.getAttributeAt(0).toString());
                }
            }

            table.ele.replaceChild(eleHeader, eleOldHeader);
            table.ele.replaceChild(eleType, eleOldType);
        } else {
            Vector<MyColumn> columns = (Vector<MyColumn>) table.GetHeader();
            NodeList listElementInTable = table.ele.getChildNodes();
            for (int i = 0; i < listObject.size(); i++) {
                MyColumn column = columns.get(i);
                TableObject object = listObject.get(i);
                if (column.Name.compareTo(object.getAttributeAt(0).toString()) != 0) {
                    if (column.Name.compareTo(table.PrimaryKey.Name) == 0) {
                        table.PrimaryKey.Name = object.getAttributeAt(0).toString();
                        table.ele.setAttribute("PrimaryKey", table.PrimaryKey.Name);
                    }

                    for (int j = 0; j < listElementInTable.getLength(); j++) {
                        Element ele = (Element) listElementInTable.item(j);
                        String oldValue = ele.getAttribute(column.Name);
                        ele.removeAttribute(column.Name);
                        ele.setAttribute(object.getAttributeAt(0).toString(), oldValue);
                    }
                }

            }
        }

        table = new MyTable(table.ele);
        tablesOfDatabase.set(indexTable, table);
        tables.set(indexDatabase, tablesOfDatabase);
    }

    boolean DeleteTBorDB(String dbName, String tbName, boolean isTable) {

        int index = -1;
        for (int i = 0; i < database.size(); i++) {
            if (database.get(i).getName().equalsIgnoreCase(dbName + ".en")) {
                index = i;
                break;
            }
        }

        if (isTable) {
            if (index != -1) {
                Element root = docs.get(index).getDocumentElement();
                Vector<MyTable> tablesOfDatabase = (Vector<MyTable>) tables.get(index);
                for (int i = 0; i < tablesOfDatabase.size(); i++) {
                    MyTable table = tablesOfDatabase.get(i);
                    if (table.GetName().compareTo(tbName) == 0) {
                        root.removeChild(table.ele);
                        tablesOfDatabase.remove(i);
                        return true;
                    }
                }
            }
        } else {

            if (index != -1) {
                tables.removeElementAt(index);
                database.get(index).delete();
                File temp = new File(database.get(index).getAbsolutePath().replace(".en", ".xml"));
                temp.delete();
                database.remove(index);
                return true;
            }
        }
        return false;
    }

    boolean AddTBorDB(String dbName, String tbName, boolean isTable) {
        if (isTable) {
            int index = -1;
            for (int i = 0; i < database.size(); i++) {
                if (database.get(i).getName().equalsIgnoreCase(dbName + ".en")) {
                    index = i;
                    break;
                }
            }

            if (index != -1) {
                MyTable newTable = new MyTable(docs.get(index), tbName, new Vector());
                Vector<MyTable> tablesOfDatabase = (Vector<MyTable>) tables.elementAt(index);
                tablesOfDatabase.add(newTable);
            }
        } else {
            try {
                String newFileName = dbName + ".en";
                File f = new File(Store, newFileName);
                database.add(f);


                DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

                // root elements
                Document doc = docBuilder.newDocument();
                Element rootElement = doc.createElement("Database");
                doc.appendChild(rootElement);
                docs.add(doc);
                Vector<MyTable> tb = new Vector<MyTable>();
                tables.add(tb);
            } catch (ParserConfigurationException ex) {
                Logger.getLogger(ServerScreen.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return true;
    }

    String[] getAllDatabaseName() {
        String[] dbNames = new String[database.size()];
        for (int i = 0; i < database.size(); i++) {
            dbNames[i] = database.get(i).getName().replace(".en", "");
        }
        return dbNames;
    }

    Vector<ServerInfo> getDatabaseInfo() {
        Vector<ServerInfo> rs = new Vector<>();
        for (int i = 0; i < database.size(); i++) {
            String dbName = database.get(i).getName().replace(".en", "");
            if (dbName.equals("AccountManager")) {
                continue;
            }
            int size = ((Vector<MyTable>) tables.get(i)).size();
            String[] tbName = new String[size];
            String[] primary = new String[size];
            Vector<String[]> columns = new Vector<>();
            Vector<String[]> types = new Vector<>();
            for (int j = 0; j < size; j++) {
                MyTable tb = ((Vector<MyTable>) tables.get(i)).get(j);
                tbName[j] = tb.GetName();
                if (tb.PrimaryKey == null) {
                    primary[j] = "";
                } else {
                    primary[j] = tb.PrimaryKey.Name;
                }

                Vector<MyColumn> column = tb.GetHeader();
                Vector<String> clName = new Vector<>();
                Vector<String> clType = new Vector<>();
                String[] names = new String[column.size()];
                String[] t = new String[column.size()];
                for (MyColumn c : column) {
                    clName.add(c.Name);
                    clType.add(c.Type);
                }
                clName.toArray(names);
                columns.add(names);
                clType.toArray(t);
                types.add(t);
            }
            ServerInfo svI = new ServerInfo(dbName, tbName, primary, columns, types);
            rs.add(svI);
        }
        return rs;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        String textER = "*Please fill all info!";
        if (info.isActive()) {
            svpass.setBackground(getBackground());
            storeAR.setBackground(getBackground());
            portSV.setBackground(getBackground());
        }
        if (cmd.equals("browse")) {

            JFileChooser fc = new JFileChooser();
            fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            int returnVal = fc.showOpenDialog(info);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File temp = fc.getSelectedFile();
                storeAR.setText(temp.getPath());
            }
        }
        if (cmd.equals("ok")) {
            String pa = svpass.getText();
            String po = portSV.getText();
            String ad = storeAR.getText();
            boolean er = false;
            if (pa.length() < 1) {
                svpass.setBackground(Color.red);
                er = true;
            }
            if (po.length() < 1 || po.length() > 5) {
                if (po.length() > 5) {
                    textER = "Port lenght must less than 6!";
                };
                portSV.setBackground(Color.red);
                er = true;
            }
            if (ad.length() < 1) {
                storeAR.setBackground(Color.red);
                er = true;
            }
            if (!er) {
                if (pa.equals(passSV)) {
                    int p = PORT;
                    try {
                        p = Integer.parseInt(po);
                        PORT = p;
                        Store = new File(ad.toString());
                        info.dispose();
                        Init();
                    } catch (Exception ex) {
                        textER = "Port must be number!";;
                        portSV.setBackground(Color.red);
                    }
                } else {
                    svpass.setBackground(Color.red);
                }
            }
        }
        if (cmd.equals("default")) {
            String pa = svpass.getText();
            if (pa.equals(passSV)) {
                info.dispose();
                Init();
            } else {
                textER = "In default mode, password must correct!";
                svpass.setBackground(Color.red);
            }
        }
        lber.setText(textER);
        if (cmd.compareToIgnoreCase("openmng") == 0) {
            Output.append("Close server!\n");
            this.DisposeThread(true);
            MainScreen main = new MainScreen("Database Manager", this);
            file.setEnabled(false);
            main.setPanel(null, null);
            main.Show();
        }
        if (cmd.compareToIgnoreCase("savedata") == 0) {
            Output.append("Close server to save data!\n");
            this.DisposeThread(true);
            SaveDatabase();
            Start();
        }
        if (cmd.compareToIgnoreCase("close") == 0) {
            Close();
        }
    }
}
